﻿using System;
using System.Reflection;
using System.Xml.Linq;
using Phoenix.Xna.Framework.Reflection;
using Phoenix.Xna.Framework.Serialization.Markup.Fields;
using Phoenix.Xna.Framework.Serialization.Markup.Objects;
using Phoenix.Xna.Framework.Serialization.Markup.Properties;

namespace Phoenix.Xna.Framework.Serialization.Markup
{
    /// <summary>
    /// Gets the markup for objects from Type information or serialized XML.
    /// </summary>
    internal class MarkupSelector
    {
        /// <summary>
        /// Gets or sets the SerializationService that provides context to references, etc.
        /// </summary>
        public Serializer Serializer { get; set; }

        /// <summary>
        /// Find the most appropriate markup object for an object model (including checking the reference manager)
        /// </summary>
        /// <param name="instance">The object instance to get the markup for.</param>
        /// <returns>The IMarkupObject for the specified object instance.</returns>
        public IMarkupObject GetMarkupObjectFor(object instance)
        {
            IMarkupObject markupObject = Serializer.ReferenceManager.GetMarkupFromInstance(instance);

            if (markupObject == null)
            {
                markupObject = GetMarkupObjectFor(instance.GetType());
                markupObject.ExtractMarkup(instance);
                Serializer.ReferenceManager.AddObject(instance, markupObject as ClassMarkup);
            }

            return markupObject;
        }

        /// <summary>
        /// Find the most appropriate markup object for xml.
        /// </summary>
        /// <param name="xmlInstance">The serialized object instance information.</param>
        /// <returns>The IMarkupObject for the specified xml.</returns>
        public IMarkupObject GetMarkupObjectFor(XElement xmlInstance)
        {
            IMarkupObject markupObject = null;

            if (ReferenceMarkup.IsReference(xmlInstance))
            {
                markupObject = new ReferenceMarkup()
                {
                    Serializer = Serializer
                };
            }
            else
            {
                Type type = NamespaceCache.GetType(xmlInstance.Name.LocalName.Replace('-', '`'), xmlInstance.Name.Namespace);
                markupObject = GetMarkupObjectFor(type);
                markupObject.ObjectType = type;
            }

            return markupObject;
        }

        /// <summary>
        /// Find the most appropriate markup object for a property.
        /// </summary>
        /// <param name="property">The PropertyInfo to get the markup for.</param>
        /// <returns>The IMarkupProperty for the specified PropertyInfo.</returns>
        public IMarkupProperty GetMarkupPropertyFor(PropertyInfo property)
        {
            IMarkupProperty markupProperty = null;

            if (property.GetFirstAttributeOfType<SerializablePropertyAttribute>(false) != null)
            {
                if (typeof(ClassMarkup).IsAssignableFrom(GetMarkupObjectFor(property.PropertyType).GetType()))
                {
                    markupProperty = new ReferenceMarkupProperty();
                }
                else
                {
                    markupProperty = new ValueMarkupProperty();
                }
            }
            else if (property.GetFirstAttributeOfType<SerializableIListAttribute>(false) != null)
            {
                markupProperty = new ListMarkupProperty();
            }
            else if (property.GetFirstAttributeOfType<SerializableIDictionaryAttribute>(false) != null)
            {
                markupProperty = new DictionaryMarkupProperty();
            }

            if (markupProperty != null)
            {
                markupProperty.Serializer = Serializer;
            }

            return markupProperty;
        }

        /// <summary>
        /// Find the most appropriate markup object for a field.
        /// </summary>
        /// <param name="property">The FieldInfo to get the markup for.</param>
        /// <returns>The IMarkupField for the specified FieldInfo.</returns>
        public IMarkupField GetMarkupFieldFor(FieldInfo field)
        {
            IMarkupField markupProperty = null;

            if (field.GetFirstAttributeOfType<SerializableFieldAttribute>(false) != null)
            {
                if (typeof(ClassMarkup).IsAssignableFrom(GetMarkupObjectFor(field.FieldType).GetType()))
                {
                    markupProperty = new ReferenceMarkupField();
                }
                else
                {
                    markupProperty = new ValueMarkupField();
                }
            }
            else if (field.GetFirstAttributeOfType<SerializableIListAttribute>(false) != null)
            {
                markupProperty = new ListMarkupField();
            }
            else if (field.GetFirstAttributeOfType<SerializableIDictionaryAttribute>(false) != null)
            {
                markupProperty = new DictionaryMarkupField();
            }

            if (markupProperty != null)
            {
                markupProperty.Serializer = Serializer;
            }

            return markupProperty;
        }

        private IMarkupObject GetMarkupObjectFor(Type type)
        {
            if (type.GetFirstAttributeOfType<SerializableIListAttribute>(false) != null || type.GetInterface("IList") != null)
            {
                return new ListMarkup()
                {
                    Serializer = Serializer
                };
            }

            if (type.GetFirstAttributeOfType<SerializableIDictionaryAttribute>(false) != null || type.GetInterface("IDictionary") != null)
            {
                return new DictionaryMarkup()
                {
                    Serializer = Serializer
                };
            }

            if (type.IsValueType || typeof(string).IsAssignableFrom(type))
            {
                return new ValueMarkup()
                {
                    Serializer = Serializer
                };
            }

            return new ClassMarkup()
            {
                Serializer = Serializer
            };
        }
    }
}
